﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Dev.Net.ServerHost.Configuration
{
	class NullableQueryStringConverter : System.ServiceModel.Dispatcher.QueryStringConverter
	{
		public override bool CanConvert(Type type)
		{
			if (IsNullable(type))
			{
				return true;
			}

			return base.CanConvert(type);
		}

		public override object ConvertStringToValue(string parameter, Type parameterType)
		{
			if (IsNullable(parameterType))
			{
				if (string.IsNullOrWhiteSpace(parameter))
				{
					return null;
				}
				return ConvertNullable(Nullable.GetUnderlyingType(parameterType), parameter);
			}

			return base.ConvertStringToValue(parameter, parameterType);
		}

		public override string ConvertValueToString(object parameter, Type parameterType)
		{
			if (IsNullable(parameterType) && parameter == null)
			{
				return null;
			}

			return base.ConvertValueToString(parameter, parameterType);
		}

		private bool IsNullable(Type type)
		{
			return (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
		}

		private object ConvertNullable(Type type, string value)
		{
			switch (Type.GetTypeCode(type))
			{
				case TypeCode.Boolean:
					return ToNullable<bool>(value);
				case TypeCode.Byte:
					return ToNullable<byte>(value);
				case TypeCode.Char:
					return ToNullable<char>(value);
				case TypeCode.DBNull:
					return DBNull.Value;
				case TypeCode.DateTime:
					return ToNullable<DateTime>(value);
				case TypeCode.Decimal:
					return ToNullable<decimal>(value);
				case TypeCode.Double:
					return ToNullable<double>(value);
				case TypeCode.Empty:
					return null;
				case TypeCode.Int16:
					return ToNullable<short>(value);
				case TypeCode.Int32:
					return ToNullable<int>(value);
				case TypeCode.Int64:
					return ToNullable<long>(value);
				case TypeCode.Object:
					return value;
				case TypeCode.SByte:
					return ToNullable<sbyte>(value);
				case TypeCode.Single:
					return ToNullable<float>(value);
				case TypeCode.String:
					return value;
				case TypeCode.UInt16:
					return ToNullable<ushort>(value);
				case TypeCode.UInt32:
					return ToNullable<uint>(value);
				case TypeCode.UInt64:
					return ToNullable<ulong>(value);
				default:
					return null;
			}
		}

		public static Nullable<T> ToNullable<T>(string value) where T : struct
		{
			Nullable<T> result = new Nullable<T>();
			try
			{
				if (!string.IsNullOrEmpty(value) && value.Trim().Length > 0)
				{
					TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
					result = (T)conv.ConvertFrom(value);
				}
			}
			catch { throw; }
			return result;
		}
	}
}
